# -*- coding: utf-8 -*-
"""
自动重启监控器 V2 - 进程分离版本
监控 reload_app.json 文件，当检测到重载请求时自动重启 run.py
监控器和应用运行在不同的进程中
"""
import os
import sys
import json
import time
import signal
import subprocess
import threading
import psutil

class AutoReloadMonitor:
    def __init__(self, app_script='run.py', check_interval=3):
        """
        初始化自动重启监控器
        
        Args:
            app_script: 应用启动脚本
            check_interval: 检查间隔（秒）
        """
        self.app_script = app_script
        self.check_interval = check_interval
        self.reload_file = 'reload_app.json'
        self.running = False
        self.app_process = None
        self.last_modified = 0
        self.app_pid = None
        # 添加进程跟踪
        self.managed_pids = set()  # 跟踪由监控器管理的进程PID
        
    def start_monitoring(self):
        """开始监控"""
        print("🚀 启动自动重启监控器 V2 (进程分离版本)...")
        print(f"📁 监控文件: {self.reload_file}")
        print(f"⏰ 检查间隔: {self.check_interval} 秒")
        print(f"🔄 应用脚本: {self.app_script}")
        print("ℹ️ 按 Ctrl+C 停止监控器")
        print("ℹ️ 监控器和应用运行在不同进程中")
        
        # 设置信号处理
        def signal_handler(signum, frame):
            print(f"\n🛑 收到信号 {signum}，正在停止监控器...")
            self.stop_monitoring()
            sys.exit(0)
        
        # 注册信号处理器
        signal.signal(signal.SIGINT, signal_handler)
        signal.signal(signal.SIGTERM, signal_handler)
        
        self.running = True
        
        # 启动应用
        if not self.start_app():
            print("❌ 应用启动失败，监控器退出")
            return
        
        # 启动监控循环
        try:
            while self.running:
                self._check_reload_file()
                self._check_app_status()
                time.sleep(self.check_interval)
        except KeyboardInterrupt:
            print("\n🛑 收到中断信号，正在停止监控器...")
            self.stop_monitoring()
            
    def _check_reload_file(self):
        """检查重载文件"""
        try:
            if os.path.exists(self.reload_file):
                # 检查文件修改时间
                current_modified = os.path.getmtime(self.reload_file)
                
                if current_modified > self.last_modified:
                    print(f"\n🔄 检测到重载配置文件更新: {time.ctime(current_modified)}")
                    
                    # 读取重载配置
                    try:
                        with open(self.reload_file, 'r', encoding='utf-8') as f:
                            reload_config = json.load(f)
                        
                        print(f"📋 重载配置:")
                        print(f"  时间戳: {time.ctime(reload_config.get('timestamp', 0))}")
                        print(f"  待注册蓝图: {reload_config.get('pending_blueprints', [])}")
                        print(f"  操作: {reload_config.get('action', 'unknown')}")
                        print(f"  原因: {reload_config.get('reason', 'unknown')}")
                        
                        # 执行重载
                        self.reload_app()
                        
                        # 删除重载配置文件
                        os.remove(self.reload_file)
                        print("✅ 重载配置文件已删除")
                        
                        # 更新最后修改时间，避免重复触发
                        self.last_modified = time.time()
                        
                    except Exception as e:
                        print(f"❌ 读取重载配置文件失败: {e}")
                        # 即使读取失败，也要更新最后修改时间
                        self.last_modified = current_modified
                else:
                    # 文件存在但未修改，更新最后修改时间
                    self.last_modified = current_modified
            else:
                # 文件不存在，重置最后修改时间
                self.last_modified = 0
                    
        except Exception as e:
            print(f"❌ 检查重载文件时发生错误: {e}")
            
    def _check_app_status(self):
        """检查应用状态"""
        if self.app_process:
            # 检查进程是否还在运行
            if self.app_process.poll() is not None:
                print(f"⚠️ 应用进程 (PID: {self.app_pid}) 已退出，退出码: {self.app_process.returncode}")
                self.app_pid = None
                self.app_process = None
                # 尝试重新启动应用
                print("🔄 尝试重新启动应用...")
                self.start_app()
        elif self.app_pid:
            # 使用 psutil 检查进程状态
            try:
                process = psutil.Process(self.app_pid)
                if not process.is_running():
                    print(f"⚠️ 应用进程 (PID: {self.app_pid}) 已退出")
                    self.app_pid = None
                    self.app_process = None
                    # 尝试重新启动应用
                    print("🔄 尝试重新启动应用...")
                    self.start_app()
            except psutil.NoSuchProcess:
                print(f"⚠️ 应用进程 (PID: {self.app_pid}) 不存在")
                self.app_pid = None
                self.app_process = None
                # 尝试重新启动应用
                print("🔄 尝试重新启动应用...")
                self.start_app()
            except Exception as e:
                print(f"❌ 检查应用状态时发生错误: {e}")
            
    def start_app(self):
        """启动应用"""
        try:
            print(f"🚀 启动应用: {self.app_script}")
            
            # 先清理所有现有的 run.py 进程
            self._cleanup_existing_run_processes()
            
            # 构建启动命令
            if self.app_script.endswith('.py'):
                cmd = [sys.executable, self.app_script]
            else:
                cmd = [sys.executable, '-m', 'flask', 'run']
                
            # 启动应用进程 - 使用独立进程，不重定向输出
            self.app_process = subprocess.Popen(
                cmd,
                # 不重定向输出，让应用正常显示和访问
                # 使用独立进程组
                creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if os.name == 'nt' else 0
            )
            
            self.app_pid = self.app_process.pid
            # 添加到管理的进程列表
            self.managed_pids.add(self.app_pid)
            print(f"✅ 应用启动成功，PID: {self.app_pid}")
            
            # 等待应用启动
            time.sleep(5)
            
            # 检查进程是否还在运行
            if self.app_process.poll() is not None:
                print(f"❌ 应用进程已退出，退出码: {self.app_process.returncode}")
                return False
                
            print("✅ 应用启动完成，可以访问")
            return True
            
        except Exception as e:
            print(f"❌ 启动应用失败: {e}")
            return False
            

            
    def reload_app(self):
        """重载应用"""
        try:
            print("🔄 开始重载应用...")
            
            # 停止当前应用
            if self.app_process and self.app_process.poll() is None:
                print(f"🛑 停止当前应用进程 (PID: {self.app_pid})")
                self.app_process.terminate()
                
                # 等待进程结束
                try:
                    self.app_process.wait(timeout=10)
                    print("✅ 应用进程已优雅停止")
                except subprocess.TimeoutExpired:
                    print("⚠️ 应用进程未及时停止，强制终止")
                    self.app_process.kill()
                    self.app_process.wait()
                    
            elif self.app_pid:
                # 使用 psutil 停止进程
                try:
                    process = psutil.Process(self.app_pid)
                    process.terminate()
                    
                    # 等待进程结束
                    try:
                        process.wait(timeout=10)
                        print("✅ 应用进程已优雅停止")
                    except psutil.TimeoutExpired:
                        print("⚠️ 应用进程未及时停止，强制终止")
                        process.kill()
                        process.wait()
                        
                except psutil.NoSuchProcess:
                    print("ℹ️ 应用进程已不存在")
                except Exception as e:
                    print(f"❌ 停止应用进程时发生错误: {e}")
                    
            # 清理进程引用
            self.app_process = None
            self.app_pid = None
            # 清理进程跟踪
            self.managed_pids.clear()
                    
            # 启动新应用（会自动清理现有进程）
            if self.start_app():
                print("✅ 应用重载完成")
            else:
                print("❌ 应用重载失败")
            
        except Exception as e:
            print(f"❌ 重载应用失败: {e}")
    
    def _cleanup_python_processes(self):
        """清理Python进程"""
        try:
            print("🧹 清理遗留的Python进程...")
            
            # 查找并终止相关的Python进程
            killed_count = 0
            for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
                try:
                    if 'python' in proc.info['name'].lower():
                        cmdline = ' '.join(proc.info['cmdline']) if proc.info['cmdline'] else ''
                        
                        # 检查是否是我们的应用进程
                        if (self.app_script in cmdline or 
                            'run.py' in cmdline or 
                            'flask' in cmdline):
                            
                            # 跳过当前监控器进程
                            if proc.info['pid'] == os.getpid():
                                continue
                                
                            # 检查是否是监控器管理的进程
                            if proc.info['pid'] in self.managed_pids:
                                print(f"🛑 终止监控器管理的进程: PID {proc.info['pid']}, 命令行: {cmdline}")
                            else:
                                print(f"🛑 终止遗留进程: PID {proc.info['pid']}, 命令行: {cmdline}")
                                
                            proc.terminate()
                            
                            try:
                                proc.wait(timeout=3)
                                killed_count += 1
                            except psutil.TimeoutExpired:
                                proc.kill()
                                proc.wait()
                                killed_count += 1
                                
                except (psutil.NoSuchProcess, psutil.AccessDenied):
                    pass
                    
            if killed_count > 0:
                print(f"✅ 清理了 {killed_count} 个遗留进程")
            else:
                print("ℹ️ 没有发现需要清理的遗留进程")
                
        except Exception as e:
            print(f"⚠️ 清理进程时发生错误: {e}")
    
    def _cleanup_managed_processes(self):
        """清理监控器管理的进程"""
        try:
            if not self.managed_pids:
                print("ℹ️ 没有需要清理的管理进程")
                return
                
            print(f"🧹 清理 {len(self.managed_pids)} 个监控器管理的进程...")
            
            killed_count = 0
            for pid in list(self.managed_pids):  # 复制列表避免修改迭代对象
                try:
                    process = psutil.Process(pid)
                    print(f"🛑 终止管理进程: PID {pid}, 名称: {process.name()}")
                    
                    process.terminate()
                    try:
                        process.wait(timeout=5)
                        killed_count += 1
                        print(f"✅ 进程 {pid} 已优雅停止")
                    except psutil.TimeoutExpired:
                        process.kill()
                        process.wait()
                        killed_count += 1
                        print(f"✅ 进程 {pid} 已强制终止")
                        
                except psutil.NoSuchProcess:
                    print(f"ℹ️ 进程 {pid} 已不存在")
                    killed_count += 1
                except Exception as e:
                    print(f"⚠️ 终止进程 {pid} 时发生错误: {e}")
                    
            if killed_count > 0:
                print(f"✅ 清理了 {killed_count} 个管理进程")
            else:
                print("ℹ️ 没有进程需要清理")
                
            # 清空管理进程列表
            self.managed_pids.clear()
                
        except Exception as e:
            print(f"⚠️ 清理管理进程时发生错误: {e}")
    
    def _cleanup_existing_run_processes(self):
        """清理所有现有的 run.py 进程"""
        try:
            print("🧹 检查并清理现有的 run.py 进程...")
            
            existing_processes = []
            for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
                try:
                    if 'python' in proc.info['name'].lower():
                        cmdline = ' '.join(proc.info['cmdline']) if proc.info['cmdline'] else ''
                        
                        # 检查是否是 run.py 进程
                        if 'run.py' in cmdline:
                            # 跳过当前监控器进程
                            if proc.info['pid'] == os.getpid():
                                continue
                                
                            existing_processes.append(proc.info)
                            
                except (psutil.NoSuchProcess, psutil.AccessDenied):
                    pass
            
            if existing_processes:
                print(f"📊 发现 {len(existing_processes)} 个现有的 run.py 进程:")
                for proc in existing_processes:
                    print(f"   PID: {proc['pid']}, 命令行: {proc['cmdline']}")
                
                # 终止所有现有的 run.py 进程
                killed_count = 0
                for proc_info in existing_processes:
                    try:
                        process = psutil.Process(proc_info['pid'])
                        print(f"🛑 终止现有进程: PID {proc_info['pid']}")
                        
                        process.terminate()
                        try:
                            process.wait(timeout=5)
                            killed_count += 1
                            print(f"✅ 进程 {proc_info['pid']} 已优雅停止")
                        except psutil.TimeoutExpired:
                            process.kill()
                            process.wait()
                            killed_count += 1
                            print(f"✅ 进程 {proc_info['pid']} 已强制终止")
                            
                    except psutil.NoSuchProcess:
                        print(f"ℹ️ 进程 {proc_info['pid']} 已不存在")
                        killed_count += 1
                    except Exception as e:
                        print(f"⚠️ 终止进程 {proc_info['pid']} 时发生错误: {e}")
                
                print(f"✅ 清理了 {killed_count} 个现有进程")
                
                # 等待一下确保进程完全清理
                time.sleep(2)
                
            else:
                print("ℹ️ 没有发现现有的 run.py 进程")
                
        except Exception as e:
            print(f"⚠️ 清理现有进程时发生错误: {e}")
            
    def stop_monitoring(self):
        """停止监控"""
        print("🛑 停止自动重启监控器...")
        self.running = False
        
        # 停止应用进程
        if self.app_process and self.app_process.poll() is None:
            try:
                print(f"🛑 停止应用进程 (PID: {self.app_pid})")
                self.app_process.terminate()
                try:
                    self.app_process.wait(timeout=5)
                    print("✅ 应用进程已优雅停止")
                except subprocess.TimeoutExpired:
                    print("⚠️ 应用进程未及时停止，强制终止")
                    self.app_process.kill()
                    self.app_process.wait()
            except Exception as e:
                print(f"⚠️ 停止应用进程时发生错误: {e}")
        elif self.app_pid:
            try:
                print(f"🛑 停止应用进程 (PID: {self.app_pid})")
                process = psutil.Process(self.app_pid)
                process.terminate()
                try:
                    process.wait(timeout=5)
                    print("✅ 应用进程已优雅停止")
                except psutil.TimeoutExpired:
                    print("⚠️ 应用进程未及时停止，强制终止")
                    process.kill()
                    process.wait()
            except Exception as e:
                print(f"⚠️ 停止应用进程时发生错误: {e}")
        
        # 清理进程引用
        self.app_process = None
        self.app_pid = None
        
        # 清理监控器管理的进程
        self._cleanup_managed_processes()
        
        # 清理所有现有的 run.py 进程
        self._cleanup_existing_run_processes()
            
        print("✅ 监控器已停止")

def main():
    """主函数"""
    print("=" * 50)
    print("🎯 自动重启监控器 V2 (进程分离版本)")
    print("=" * 50)
    
    # 检查 psutil 依赖
    try:
        import psutil
    except ImportError:
        print("❌ 缺少 psutil 依赖，请安装: pip install psutil")
        return
    
    # 获取应用脚本
    app_script = 'run.py'
    if len(sys.argv) > 1:
        app_script = sys.argv[1]
        
    # 创建监控器
    monitor = AutoReloadMonitor(app_script)
    
    # 启动监控
    monitor.start_monitoring()

if __name__ == '__main__':
    main() 